package com.xyy.saas.payment.util.xydSign;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

@Slf4j
public class EncryptUtils {

    /**
     * MAC算法可选以下多种算法
     * <pre>
     * SHA
     * RSA
     * HmacMD5
     * HmacSHA1
     * HmacSHA256
     * HmacSHA384
     * HmacSHA512
     * MD5withRSA
     * SHA256WithRSA
     * </pre>
     */
    public static final String KEY_SHA = "SHA";
    public static final String CHAR_ENCODING = "UTF-8";
    public static final String KEY_SH1 = "SHA-1";
    public static final String KEY_RSA = "RSA";
    public static final String KEY_MD5 = "MD5";
    public static final String KEY_MAC = "HmacMD5";
    public static final String KEY_SHA_RSA = "SHA1withRSA";
    public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
    public static final String KEY_MD5_RSA = "MD5withRSA";
    public static final String KEY_SHA256_RSA = "SHA256WithRSA";

    /**
     * BASE64解密
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptBASE64(String key) {
        return Base64.getDecoder().decode(key);
    }

    /**
     * BASE64加密
     *
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptBASE64(byte[] key) {
        return Base64.getEncoder().encodeToString(key);
    }

    /**
     * 初始化HMAC密钥
     *
     * @return
     * @throws Exception
     */
    public static String initMacKey() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
        SecretKey secretKey = keyGenerator.generateKey();
        return encryptBASE64(secretKey.getEncoded());
    }

    /**
     * MAC加密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
        SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
        Mac mac = Mac.getInstance(secretKey.getAlgorithm());
        mac.init(secretKey);
        byte[] bytes = mac.doFinal(data);
        return bytes;

    }

    /**
     * @param origin    加密数据
     * @param charset   字符编码
     * @param algorithm 加密算法
     * @return
     */
    public static String encode(String origin, String charset, String algorithm) {
        try {
            byte[] digest = encode(origin.getBytes(StringUtils.isNotBlank(charset) ? charset : CHAR_ENCODING), algorithm);
            return Byte2HexUtil.byteArrayToHexString(digest);
        } catch (Exception exception) {
        }
        return null;
    }

    /**
     * @param data      加密数据
     * @param algorithm 加密算法
     * @return
     */
    public static byte[] encode(byte[] data, String algorithm) {
        try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            byte[] digest = md.digest(data);
            return digest;
        } catch (Exception exception) {
        }
        return null;
    }

    /**
     * RSA签名
     *
     * @param content    待签名数据
     * @param privateKey 商户私钥
     * @param charset    编码格式
     * @return 签名值
     */
    public static String sign(String content, String privateKey, String signAlgorithm, String charset) throws Exception {
        String md5Data = EncryptUtils.encode(content, EncryptUtils.CHAR_ENCODING, EncryptUtils.KEY_SH1);
        return sign(md5Data.getBytes(), privateKey, signAlgorithm);
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       加密数据
     * @param privateKey 私钥
     * @return 数字签名
     * @throws Exception 异常
     */
    public static String sign(byte[] data, String privateKey, String signAlgorithm) throws Exception {

        // 解密由base64编码的私钥
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        // 构造PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);
        // 取私钥匙对象
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);

        // 用私钥对信息生成数字签名
        Signature signature = Signature.getInstance(signAlgorithm);
        signature.initSign(priKey);
        signature.update(data);

        return Base64.getEncoder().encodeToString(signature.sign());
    }

    public static String encryptAES(String data, String privateKey) throws Exception {
        AssertUtils.isNotBlank(privateKey, "秘钥不能为空");
        AssertUtils.isTrue(privateKey.length() >= 16, "秘钥长度不为16位");
        byte[] aseKey = privateKey.substring(0, 16).getBytes(CHAR_ENCODING);
        return encryptAES(data, aseKey);
    }

    public static String encryptAES(String data, byte[] privateKey) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(privateKey, "AES");
        //"算法/模式/补码方式"
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(data.getBytes(CHAR_ENCODING));
        //此处使用BASE64做转码功能，同时能起到2次加密的作用。
        return encryptBASE64(encrypted);
    }

    /**
     * 校验数字签名
     *
     * @param data      加密数据
     * @param publicKey 公钥
     * @param sign      数字签名
     * @return 校验成功返回true 失败返回false
     * @throws Exception 异常
     */
    public static boolean verify(String data, String publicKey, String sign, String signAlgorithm, String charset)
            throws Exception {
        // 验证签名是否正常
        return verify(data.getBytes(StringUtils.isNotBlank(charset) ? CHAR_ENCODING : charset), publicKey, sign, signAlgorithm);
    }


    /**
     * 校验数字签名
     *
     * @param data      加密数据
     * @param publicKey 公钥
     * @param sign      数字签名
     * @return 校验成功返回true 失败返回false
     * @throws Exception 异常
     */
    public static boolean verify(byte[] data, String publicKey, String sign, String signAlgorithm)
            throws Exception {

        AssertUtils.isNotBlank(sign, "签名不能为空");
        // 解密由base64编码的公钥
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);

        // 构造X509EncodedKeySpec对象
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);

        // KEY_ALGORITHM 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA);

        // 取公钥匙对象
        PublicKey pubKey = keyFactory.generatePublic(keySpec);

        Signature signature = Signature.getInstance(signAlgorithm);
        signature.initVerify(pubKey);
        signature.update(data);

        // 验证签名是否正常
        return signature.verify(Base64.getDecoder().decode(sign));
    }


    /**
     * 解密
     *
     * @param content       密文
     * @param private_key   商户私钥
     * @param input_charset 编码格式
     * @return 解密后的字符串
     */
    public static String decrypt(String content, String private_key, String input_charset) throws Exception {
        PrivateKey prikey = getPrivateKey(private_key);

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, prikey);

        InputStream ins = new ByteArrayInputStream(Base64.getDecoder().decode(content));
        ByteArrayOutputStream writer = new ByteArrayOutputStream();
        //rsa解密的字节大小最多是128，将需要解密的内容，按128位拆开解密
        byte[] buf = new byte[128];
        int bufl;

        while ((bufl = ins.read(buf)) != -1) {
            byte[] block = null;
            if (buf.length == bufl) {
                block = buf;
            } else {
                block = new byte[bufl];
                for (int i = 0; i < bufl; i++) {
                    block[i] = buf[i];
                }
            }
            writer.write(cipher.doFinal(block));
        }
        return new String(writer.toByteArray(), input_charset);
    }


    /**
     * 得到私钥
     *
     * @param key 密钥字符串（经过base64编码）
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String key) throws Exception {

        byte[] keyBytes;
        keyBytes = Base64.getDecoder().decode(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }


    /**
     * 验签数据
     *
     * @return
     */
    public static boolean verify(String publicKey, String timestamp, String body, String originSignData) throws Exception {
        log.info("publicKey:{},timestamp:{},body：{},originSignData:{}", publicKey, timestamp, body, originSignData);
        String md5Data = getMd5SignData(timestamp, body);
        // 验签
        boolean verify = EncryptUtils.verify(md5Data.getBytes(), publicKey, originSignData, EncryptUtils.KEY_SHA_RSA);
        if(!verify){
            // 兼容历史 转json在解密
            md5Data = getMd5SignDataFormatJson(timestamp, body);
            verify = EncryptUtils.verify(md5Data.getBytes(), publicKey, originSignData, EncryptUtils.KEY_SHA_RSA);
        }
        return verify;
    }

    /**
     * 验签数据
     *
     * @return
     */
    public static boolean verify(String appkey, String appsecret, String publicKey, String securityTypeCode, String timestamp, String body, String originSignData) throws Exception {
        String md5Data = getMd5SignData(appkey, appsecret, securityTypeCode, timestamp, body);
        // 验签
        boolean verify = EncryptUtils.verify(md5Data.getBytes(), publicKey, originSignData, EncryptUtils.KEY_SHA_RSA);
        return verify;
    }

    public static String getMd5SignData(String timestamp, String body) {
        return getMd5SignData(null, null, null, timestamp, body);
    }

    public static String getMd5SignDataFormatJson(String timestamp, String body) {
        return getMd5SignDataFormatJson(null, null, null, timestamp, body);
    }
    public static String getMd5SignData(String appkey, String appsecret, String securityTypeCode, String timestamp, String body) {
        // 1,签名固定参数：appkey、appsecret，securityTypeCode、timestamp（时间戳10位）、body（http请求体）
        Map<String, String> paramMap = new TreeMap<>();
        if (StringUtils.isNotBlank(appkey)) {
            paramMap.put("appkey", appkey);
        }
        if (StringUtils.isNotBlank(appsecret)) {
            //  兼容3.0
            paramMap.put("appsecret", appsecret);
        }
        if (StringUtils.isNotBlank(securityTypeCode)) {
            //  兼容3.0
            paramMap.put("securityTypeCode", securityTypeCode);
        }
        paramMap.put("timestamp", timestamp);
        if (body != null) {
            paramMap.put("body", body);
            //  paramMap.put("body", JSON.parseObject(body).toJSONString());
        }
        // 2,针对1步要求的所有非空的参数按字段名的ASCLL码从小到大排序后，使用URL键值对的格式（如key1=value1&key2=value2）拼接成字符串
        // 。字段名和字段值都采用原始值不进行URL转义
        //log.info("paramMap:{}", JSON.toJSONString(body));
        String signData = createReqParam(paramMap);
        //log.info("signData:{}", signData);
        // 3,针对第2步得到的结果进行SHA-1加密
        String md5Data = encode(signData, EncryptUtils.CHAR_ENCODING, EncryptUtils.KEY_SH1);
        //log.info("signData-md5Data:{}", md5Data);
        return md5Data;
    }

    public static String getMd5SignDataFormatJson(String appkey, String appsecret, String securityTypeCode, String timestamp, String body) {
        // 1,签名固定参数：appkey、appsecret，securityTypeCode、timestamp（时间戳10位）、body（http请求体）
        Map<String, String> paramMap = new TreeMap<>();
        if (StringUtils.isNotBlank(appkey)) {
            paramMap.put("appkey", appkey);
        }
        if (StringUtils.isNotBlank(appsecret)) {
            //  兼容3.0
            paramMap.put("appsecret", appsecret);
        }
        if (StringUtils.isNotBlank(securityTypeCode)) {
            //  兼容3.0
            paramMap.put("securityTypeCode", securityTypeCode);
        }
        paramMap.put("timestamp", timestamp);
        if (body != null) {
              paramMap.put("body", JSON.parseObject(body).toJSONString());
        }
        // 2,针对1步要求的所有非空的参数按字段名的ASCLL码从小到大排序后，使用URL键值对的格式（如key1=value1&key2=value2）拼接成字符串
        // 。字段名和字段值都采用原始值不进行URL转义
        log.info("paramMap:{}", JSON.toJSONString(body));
        String signData = createReqParam(paramMap);
        log.info("signData:{}", signData);
        // 3,针对第2步得到的结果进行SHA-1加密
        String md5Data = encode(signData, EncryptUtils.CHAR_ENCODING, EncryptUtils.KEY_SH1);
        log.info("signData-md5Data:{}", md5Data);
        return md5Data;
    }


    public static String createReqParam(Map params) {
        TreeMap treeMap = new TreeMap();
        treeMap.putAll(params);
        StringBuffer sb = new StringBuffer();
        if (treeMap != null && treeMap.size() > 0) {
            Iterator var2 = treeMap.entrySet().iterator();

            while (var2.hasNext()) {
                Map.Entry<String, Object> entry = (Map.Entry) var2.next();
                if (entry.getKey() != null && !(entry.getKey()).isEmpty() && entry.getValue() != null) {
                    String k = entry.getKey();
                    String v = "" + entry.getValue();
                    sb.append(k + "=" + v + "&");
                }
            }
        }

        sb = sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }

}
